from Goldenberry.optimization.ga.GbSolutionRepMgr.Encoder import Encoder
import numpy as np
import math

class DirectRealMapping(Encoder):
    """The phenotype is the same thet the genotype, not encoding required."""
     
    def encode_phenotype(self,real):
       return real
    def decode_genotype(self,real):
        return real 

class DirectMappingEscaledUnitInterval(Encoder):
    """The phenotype is the same thet the genotype, not encoding required."""
    
    min = 0
    max = 1
    scale_factor = 0
    displacement = 0

    def __init__(self, min=0, max=1):
        if (min>max):
            raise Exception("Min can not be bigger than Max")
        self.max=max
        self.min=min
        self.displacement=min
        lenght=abs(max-min)
        self.scale_factor= 1.0/lenght

    def encode_phenotype(self,values):
        values=np.subtract(values,(self.displacement))
        values=np.multiply(values,(self.scale_factor))
        return values

    def decode_genotype(self,values):
        values=np.divide(values,self.scale_factor)
        values=np.add(values,self.displacement)
        return values
        
class FloatToBinary(Encoder):
    """Counts the number of ones in the candidate solution, starting at the beginning, until a zero is encountered."""
     #Atributtes
    max_scaled=0.0
    scale_factor=0.0
    displacement=0.0
    multiplication_factor=0.0
    binary_dimension=0
    """The function receives the minimu, and maximum function domain
    and calculate the number of bits to representate the function; 
    it is importan to put all decimal places in order to calculate the precision that you 
    want to manage to"""
    def __init__(self, minimum=0.0, maximum=1.0):
        if (minimum>maximum):#Ensure that min is not bigger than max
            raise Exception("Min can not be bigger than Max")
        
        min_dec = str(abs(minimum-int(minimum)))[2:] #Gets the numbers after the point precision
        max_dec = str(abs(maximum-int(maximum)))[2:]
        nmindec=0
        nmaxdec=0
        if(min_dec!="0"):
            nmindec= len(min_dec)#Obtain the number of decimals or precision in the domain function
        if(max_dec!="0"):
            nmaxdec= len(max_dec)
        longerdec=   max(nmindec,nmaxdec)#Gets the largest number of decimals
        self.multiplication_factor=math.pow( 10, longerdec )#Calculate the multiplicator number to convert the floating point number to integer
                                                            #Example: 2.056 have to be multiplicated by 1000 
        self.displacement=round(minimum*self.multiplication_factor)#Use round Python introduce a decimal representation error https://docs.python.org/2/tutorial/floatingpoint.html
                                                                   #Displacement is the distance from the minumum number from domain function to 0
        lenght=(abs(maximum)*self.multiplication_factor)-self.displacement#Now it is necessary to know the max positive integer number for the function 
                                                                          # example: a function [-5.11,5.12] the max positive number is : (5.12*100)+511=1023
        binary_length=int(lenght).bit_length()#Calculates the number of bits to representate the biggest scaled number for the function; i.a 1023 need 10 bits
        self.binary_dimension=binary_length
        self.max_scaled=math.pow(2, binary_length)#Calculates the integer number generated by a bit array with all ones (1); i.a 10 bits gets 1024
        self.scale_factor = self.max_scaled/(lenght+1.0)#scale factor is the number to be multiplicated to convert the real numbers to the integer binary scaled representation
                                                        #lenght+1 ensure that number 0 will be represented so in the case that we have 1024 as max number generated by bits
                                                        #and 1024 lenght of the function means that we have (1:1) scaled factor, each number of the function have its representation 
                                                        #into the binary representation.
                                                        #Another example, given a function [-600,600] the lenght of the function is 1200 tho representate this number 
                                                        #its necesarry an array bit of 11 so binary_length=11, but the number that can be generated by 11 bits is 2048
                                                        #so the scale factor is 2048/1201 =1.705245629 this ensure the use of all bits in the array block
                                                        
        
    """Get an scaled number given a real number one using the scale factor calculated from the domain of the function;
    the scaled number can be representated by a binary array with dimension d but can not exceed the decimal numbers 
    given in the domain function at the constructor in other case the precision will be reduced"""
    def getScaledNumber(self,number):
        result = ((number * self.multiplication_factor - self.displacement)*self.scale_factor )
        if (result>self.max_scaled or result<0):
            raise Exception("value out of function domain")
        return int(result)#The result is affected by this double convertion to int


    """Return a real number given an scaled positive integer number"""
    def getRestoredNumber(self, number):
        result=   ((number/self.scale_factor)+self.displacement)/self.multiplication_factor
        return result
    
    def encode_phenotype(self, doubleArray):
        binary_str=""
        result=[]
        for num in  doubleArray:
            int_num=self.getScaledNumber(num)#Obtain a scaled number from the phenotype one
            bin =np.binary_repr(int_num)#Gets a binary representation for the positive integer
            for i in range(len(bin),self.binary_dimension):#If the lenght for the binary array is less than binary dimension, adds 0
                bin="0"+bin
            binary_str+=bin
        for letter in binary_str:  result.append(int(letter))#Convert the string to a 0s and 1s array

        return np.array(result)
    
    """Gets a real array number given a numpy binary array"""
    def decode_genotype(self, binaryArray):
        binary_str=""
        index=0
        result=[]
        for num in binaryArray:
            binary_str+=str(num)
            index+=1
            if index == self.binary_dimension:#When the block is completed (Genotype)
                number=int(binary_str,2)#Gets the number from the binarry arry
                result.append(self.getRestoredNumber(number))#Gets the Phenotype representation from the genotype
                binary_str=""
                index=0
        return np.array(result)
        
    